$wgSearchTypeAlternatives = null;
/**
- * Table name prefix; this should be alphanumeric and not contain spaces nor hyphens
+ * Table name prefix.
+ * This should be alphanumeric, contain neither spaces nor hyphens, and end in "_"
*/
$wgDBprefix = '';
const MAX_CACHE_TTL = 300; // 5 minutes
const MAX_SIGNATURE_TTL = 60;
+ const MAX_CACHE_RECENT = 2;
+
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
);
}
+ if ( $ok ) {
+ // These blobs can waste slots in low cardinality memcached slabs
+ self::pruneExcessStashedEntries( $cache, $user, $key );
+ }
+
return $ok ? true : 'store_error';
}
+ /**
+ * @param BagOStuff $cache
+ * @param User $user
+ * @param string $newKey
+ */
+ private static function pruneExcessStashedEntries( BagOStuff $cache, User $user, $newKey ) {
+ $key = $cache->makeKey( 'stash-edit-recent', $user->getId() );
+
+ $keyList = $cache->get( $key ) ?: [];
+ if ( count( $keyList ) >= self::MAX_CACHE_RECENT ) {
+ $oldestKey = array_shift( $keyList );
+ $cache->delete( $oldestKey );
+ }
+
+ $keyList[] = $newKey;
+ $cache->set( $key, $keyList, 2 * self::MAX_CACHE_TTL );
+ }
+
public function getAllowedParams() {
return [
'title' => [
private $mFieldInfoCache = [];
function __construct( array $p ) {
- global $wgDBprefix;
-
- if ( $p['tablePrefix'] == 'get from global' ) {
- $p['tablePrefix'] = $wgDBprefix;
- }
$p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
parent::__construct( $p );
Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
'defaultGroup' => $mainConfig->get( 'DBDefaultGroup' ),
];
+ $serversCheck = [];
// When making changes here, remember to also specify MediaWiki-specific options
// for Database classes in the relevant Installer subclass.
// Such as MysqlInstaller::openConnection and PostgresInstaller::openConnectionWithParams.
if ( isset( $lbConf['servers'] ) ) {
// Server array is already explicitly configured; leave alone
} elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
+ $lbConf['servers'] = [];
foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
if ( $server['type'] === 'sqlite' ) {
$server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ];
'port' => $mainConfig->get( 'DBport' ),
'useWindowsAuth' => $mainConfig->get( 'DBWindowsAuthentication' )
];
- } elseif ( $server['type'] === 'mysql' ) {
- // A DB name is not needed to connect to mysql; 'dbname' is useless.
- // This field only defines the DB to use for unspecified DB domains.
- $ldDB = $mainConfig->get( 'DBname' ); // local domain DB
- $srvDB = $server['dbname'] ?? null; // server DB
- if ( $srvDB !== null && $srvDB !== $ldDB ) {
- self::reportMismatchedDBs( $srvDB, $ldDB );
- }
- }
-
- $ldTP = $mainConfig->get( 'DBprefix' ); // local domain prefix
- $srvTP = $server['tablePrefix'] ?? ''; // server table prefix
- if ( $srvTP !== '' && $srvTP !== $ldTP ) {
- self::reportMismatchedPrefixes( $srvTP, $ldTP );
}
if ( in_array( $server['type'], $typesWithSchema, true ) ) {
if ( !isset( $lbConf['externalClusters'] ) ) {
$lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' );
}
+
+ $serversCheck = $lbConf['servers'];
} elseif ( $lbConf['class'] === Wikimedia\Rdbms\LBFactoryMulti::class ) {
if ( isset( $lbConf['serverTemplate'] ) ) {
if ( in_array( $lbConf['serverTemplate']['type'], $typesWithSchema, true ) ) {
}
$lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
}
+ $serversCheck = $lbConf['serverTemplate'] ?? [];
}
+ self::sanityCheckServerConfig( $serversCheck, $mainConfig );
$lbConf = self::applyDefaultCaching( $lbConf, $srvCace, $mainStash, $wanCache );
return $lbConf;
return $lbConf;
}
+ /**
+ * @param array $servers
+ * @param Config $mainConfig
+ */
+ private static function sanityCheckServerConfig( array $servers, Config $mainConfig ) {
+ $ldDB = $mainConfig->get( 'DBname' ); // local domain DB
+ $ldTP = $mainConfig->get( 'DBprefix' ); // local domain prefix
+
+ foreach ( $servers as $server ) {
+ $type = $server['type'] ?? null;
+ $srvDB = $server['dbname'] ?? null; // server DB
+ $srvTP = $server['tablePrefix'] ?? ''; // server table prefix
+
+ if ( $type === 'mysql' ) {
+ // A DB name is not needed to connect to mysql; 'dbname' is useless.
+ // This field only defines the DB to use for unspecified DB domains.
+ if ( $srvDB !== null && $srvDB !== $ldDB ) {
+ self::reportMismatchedDBs( $srvDB, $ldDB );
+ }
+ } elseif ( $type === 'postgres' ) {
+ if ( $srvTP !== '' ) {
+ self::reportIfPrefixSet( $srvTP, $type );
+ }
+ }
+
+ if ( $srvTP !== '' && $srvTP !== $ldTP ) {
+ self::reportMismatchedPrefixes( $srvTP, $ldTP );
+ }
+ }
+ }
+
+ /**
+ * @param string $prefix Table prefix
+ * @param string $dbType Database type
+ */
+ private static function reportIfPrefixSet( $prefix, $dbType ) {
+ $e = new UnexpectedValueException(
+ "\$wgDBprefix is set to '$prefix' but the database type is '$dbType'. " .
+ "MediaWiki does not support using a table prefix with this RDBMS type."
+ );
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+ exit;
+ }
+
/**
* @param string $srvDB Server config database
* @param string $ldDB Local DB domain database
throw new DBUnexpectedError( $this->conn, 'Cannot close shared connection.' );
}
- public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+ public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
/** @var int No transaction is active */
const STATUS_TRX_NONE = 3;
+ /** @var int Writes to this temporary table do not affect lastDoneWrites() */
+ const TEMP_NORMAL = 1;
+ /** @var int Writes to this temporary table effect lastDoneWrites() */
+ const TEMP_PSEUDO_PERMANENT = 2;
+
/**
* @note exceptions for missing libraries/drivers should be thrown in initConnection()
* @param array $params Parameters passed from Database::factory()
/**
* @param string $sql A SQL query
- * @return bool Whether $sql is SQL for TEMPORARY table operation
+ * @param bool $pseudoPermanent Treat any table from CREATE TEMPORARY as pseudo-permanent
+ * @return int|null A self::TEMP_* constant for temp table operations or null otherwise
*/
- protected function registerTempTableOperation( $sql ) {
+ protected function registerTempTableWrite( $sql, $pseudoPermanent ) {
+ static $qt = '[`"\']?(\w+)[`"\']?'; // quoted table
+
if ( preg_match(
- '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+ '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?' . $qt . '/i',
$sql,
$matches
) ) {
- $this->sessionTempTables[$matches[1]] = 1;
+ $type = $pseudoPermanent ? self::TEMP_PSEUDO_PERMANENT : self::TEMP_NORMAL;
+ $this->sessionTempTables[$matches[1]] = $type;
- return true;
+ return $type;
} elseif ( preg_match(
- '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+ '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
$sql,
$matches
) ) {
- $isTemp = isset( $this->sessionTempTables[$matches[1]] );
+ $type = $this->sessionTempTables[$matches[1]] ?? null;
unset( $this->sessionTempTables[$matches[1]] );
- return $isTemp;
+ return $type;
} elseif ( preg_match(
- '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+ '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
$sql,
$matches
) ) {
- return isset( $this->sessionTempTables[$matches[1]] );
+ return $this->sessionTempTables[$matches[1]] ?? null;
} elseif ( preg_match(
- '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
+ '/^(?:(?:INSERT|REPLACE)\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+' . $qt . '/i',
$sql,
$matches
) ) {
- return isset( $this->sessionTempTables[$matches[1]] );
+ return $this->sessionTempTables[$matches[1]] ?? null;
}
- return false;
+ return null;
}
- public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+ public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
$this->assertTransactionStatus( $sql, $fname );
$this->assertHasConnectionHandle();
+ $flags = (int)$flags; // b/c; this field used to be a bool
+ $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
+ $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
+
$priorTransaction = $this->trxLevel;
$priorWritesPending = $this->writesOrCallbacksPending();
$this->lastQuery = $sql;
# In theory, non-persistent writes are allowed in read-only mode, but due to things
# like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
$this->assertIsWritableMaster();
- # Avoid treating temporary table operations as meaningful "writes"
- $isEffectiveWrite = !$this->registerTempTableOperation( $sql );
+ # Do not treat temporary table writes as "meaningful writes" that need committing.
+ # Profile them as reads. Integration tests can override this behavior via $flags.
+ $tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent );
+ $isEffectiveWrite = ( $tableType !== self::TEMP_NORMAL );
} else {
$isEffectiveWrite = false;
}
$this->trxStatus = self::STATUS_TRX_ERROR;
$this->trxStatusCause =
$this->getQueryExceptionAndLog( $lastError, $lastErrno, $sql, $fname );
- $tempIgnore = false; // cannot recover
+ $ignoreErrors = false; // cannot recover
$this->trxStatusIgnoredCause = null;
}
}
- $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
+ $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $ignoreErrors );
}
return $this->resultObject( $ret );
/**
* Report a query error. Log the error, and if neither the object ignore
- * flag nor the $tempIgnore flag is set, throw a DBQueryError.
+ * flag nor the $ignoreErrors flag is set, throw a DBQueryError.
*
* @param string $error
* @param int $errno
* @param string $sql
* @param string $fname
- * @param bool $tempIgnore
+ * @param bool $ignoreErrors
* @throws DBQueryError
*/
- public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- if ( $tempIgnore ) {
+ public function reportQueryError( $error, $errno, $sql, $fname, $ignoreErrors = false ) {
+ if ( $ignoreErrors ) {
$this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
} else {
$exception = $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
$this->indexAliases = $aliases;
}
+ /**
+ * @param int $field
+ * @param int $flags
+ * @return bool
+ */
+ protected function hasFlags( $field, $flags ) {
+ return ( ( $field & $flags ) === $flags );
+ }
+
/**
* Get the underlying binding connection handle
*
*/
public function __construct( $database, $schema, $prefix ) {
if ( $database !== null && ( !is_string( $database ) || !strlen( $database ) ) ) {
- throw new InvalidArgumentException( "Database must be null or a non-empty string." );
+ throw new InvalidArgumentException( 'Database must be null or a non-empty string.' );
}
$this->database = $database;
if ( $schema !== null && ( !is_string( $schema ) || !strlen( $schema ) ) ) {
- throw new InvalidArgumentException( "Schema must be null or a non-empty string." );
+ throw new InvalidArgumentException( 'Schema must be null or a non-empty string.' );
}
$this->schema = $schema;
if ( !is_string( $prefix ) ) {
- throw new InvalidArgumentException( "Prefix must be a string." );
+ throw new InvalidArgumentException( 'Prefix must be a string.' );
+ } elseif ( $prefix !== '' && substr( $prefix, -1, 1 ) !== '_' ) {
+ throw new InvalidArgumentException( 'A non-empty prefix must end with "_".' );
}
$this->prefix = $prefix;
}
return true; // even the prefix doesn't matter
}
- $other = ( $other instanceof self ) ? $other : self::newFromId( $other );
+ $other = self::newFromId( $other );
return (
( $this->database === $other->database || $this->database === null ) &&
* @return string
*/
private function convertToString() {
- $parts = [ $this->database ];
+ $parts = [ (string)$this->database ];
if ( $this->schema !== null ) {
$parts[] = $this->schema;
}
/**
* @param string $name
- * @param string $format
- * @return string
+ * @param string $format One of "quoted" (default), "raw", or "split".
+ * @return string|array When the requested $format is "split", a list of database, schema, and
+ * table name is returned. Database and schema can be `false`.
*/
function tableName( $name, $format = 'quoted' ) {
# Replace reserved words with better ones
/**
* call this instead of tableName() in the updater when renaming tables
* @param string $name
- * @param string $format One of quoted, raw, or split
- * @return string
+ * @param string $format One of "quoted" (default), "raw", or "split".
+ * @return string|array When the requested $format is "split", a list of database, schema, and
+ * table name is returned. Database and schema can be `false`.
+ * @private
*/
function realTableName( $name, $format = 'quoted' ) {
$table = parent::tableName( $name, $format );
if ( $format == 'split' ) {
// Used internally, we want the schema split off from the table name and returned
// as a list with 3 elements (database, schema, table)
- $table = explode( '.', $table );
- while ( count( $table ) < 3 ) {
- array_unshift( $table, false );
- }
+ return array_pad( explode( '.', $table, 3 ), -3, false );
}
return $table;
}
}
}
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $domain->getSchema() !== null ) {
+ throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ }
+
+ $database = $domain->getDatabase();
+ // A null database means "don't care" so leave it as is and update the table prefix
+ if ( $database === null ) {
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ null,
+ $domain->getTablePrefix()
+ );
+
+ return true;
+ }
+
+ if ( $database !== $this->getDBname() ) {
+ $sql = 'USE ' . $this->addIdentifierQuotes( $database );
+ $ret = $this->doQuery( $sql );
+ if ( $ret === false ) {
+ $error = $this->lastError();
+ $errno = $this->lastErrno();
+ $this->reportQueryError( $error, $errno, $sql, __METHOD__ );
+ }
+ }
+
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
+
+ return true;
+ }
+
/**
* Open a connection to a MySQL server
*
$oldName = $this->addIdentifierQuotes( $oldName );
$query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
- return $this->query( $query, $fname );
+ return $this->query( $query, $fname, $this::QUERY_PSEUDO_PERMANENT );
}
/**
return $conn->affected_rows;
}
- function doSelectDomain( DatabaseDomain $domain ) {
- if ( $domain->getSchema() !== null ) {
- throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
- }
-
- $database = $domain->getDatabase();
- if ( $database !== $this->getDBname() ) {
- $conn = $this->getBindingHandle();
- if ( !$conn->select_db( $database ) ) {
- throw new DBExpectedError( $this, "Could not select database '$database'." );
- }
- }
-
- // Update that domain fields on success (no exception thrown)
- $this->currentDomain = $domain;
-
- return true;
- }
-
/**
* @param mysqli_result $res
* @return bool
$connectVars['port'] = (int)$this->port;
}
if ( $this->flags & self::DBO_SSL ) {
- $connectVars['sslmode'] = 1;
+ $connectVars['sslmode'] = 'require';
}
$this->connectString = $this->makeConnectionString( $connectVars );
$temporary = $temporary ? 'TEMPORARY' : '';
- $ret = $this->query( "CREATE $temporary TABLE $newNameE " .
- "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)", $fname );
+ $ret = $this->query(
+ "CREATE $temporary TABLE $newNameE " .
+ "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)",
+ $fname,
+ $this::QUERY_PSEUDO_PERMANENT
+ );
if ( !$ret ) {
return $ret;
}
$fieldE = $this->addIdentifierQuotes( $field );
$newSeqE = $this->addIdentifierQuotes( $newSeq );
$newSeqQ = $this->addQuotes( $newSeq );
- $this->query( "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE", $fname );
+ $this->query(
+ "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE",
+ $fname
+ );
$this->query(
"ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
$fname
}
}
- $res = $this->query( $sql, $fname );
+ $res = $this->query( $sql, $fname, self::QUERY_PSEUDO_PERMANENT );
// Take over indexes
$indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' );
/** @var int Enable compression in connection protocol */
const DBO_COMPRESS = 512;
+ /** @var int Ignore query errors and return false when they happen */
+ const QUERY_SILENCE_ERRORS = 1; // b/c for 1.32 query() argument; note that (int)true = 1
+ /**
+ * @var int Treat the TEMPORARY table from the given CREATE query as if it is
+ * permanent as far as write tracking is concerned. This is useful for testing.
+ */
+ const QUERY_PSEUDO_PERMANENT = 2;
+
/**
* A string describing the current software version, and possibly
* other details in a user-friendly way. Will be listed on Special:Version, etc.
* @param string $sql SQL query
* @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
* comment (you can use __METHOD__ or add some extra info)
- * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
- * maybe best to catch the exception instead?
+ * @param int $flags Bitfield of IDatabase::QUERY_* constants. Note that suppression
+ * of errors is best handled by try/catch rather than using one of these flags.
* @return bool|IResultWrapper True for a successful write query, IResultWrapper object
- * for a successful read query, or false on failure if $tempIgnore set
+ * for a successful read query, or false on failure if QUERY_SILENCE_ERRORS is set.
* @throws DBError
*/
- public function query( $sql, $fname = __METHOD__, $tempIgnore = false );
+ public function query( $sql, $fname = __METHOD__, $flags = 0 );
/**
* Free a result object returned by query() or select(). It's usually not
public function free() {
if ( $this->db ) {
- $this->db->freeResult( $this );
$this->db = null;
}
$this->result = null;
public function __construct( IDatabase $db, $error, $errno, $sql, $fname, $message = null ) {
if ( $message === null ) {
if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) {
- $message = "A connection error occurred. \n" .
+ $message = "A connection error occurred during a query. \n" .
"Query: $sql\n" .
"Function: $fname\n" .
"Error: $errno $error\n";
$res = $dbw->query( "SELECT COUNT(*) AS count FROM $links" );
$row = $dbw->fetchObject( $res );
$numRows = $row->count;
- $dbw->freeResult( $res );
if ( $numRows == 0 ) {
$this->output( "Updating schema (no rows to convert)...\n" );
}
}
}
- $dbw->freeResult( $res );
$dbw->bufferResults( true );
$this->output( "Finished loading IDs.\n\n" );
$this->performanceLog(
$numBadLinks++;
}
}
- $dbw->freeResult( $res );
# $this->output( "rowOffset: $rowOffset\ttuplesAdded: "
# . "$tuplesAdded\tnumBadLinks: $numBadLinks\n" );
if ( $tuplesAdded != 0 ) {
foreach ( $res as $row ) {
$this->oldIdMap[$row->rev_id] = $row->rev_text_id;
}
- $dbr->freeResult( $res );
if ( !count( $this->oldIdMap ) ) {
continue;
$this->addError( 'unfixable', "Error: invalid flags field \"$flags\"", $id );
}
}
- $dbr->freeResult( $res );
// Output errors for any missing text rows
foreach ( $missingTextRows as $oldId => $revId ) {
$externalNormalBlobs[$cluster][$id][] = $row->old_id;
}
}
- $dbr->freeResult( $res );
}
// Check external concat blobs for the right header
foreach ( $res as $row ) {
unset( $xBlobIds[$row->blob_id] );
}
- $extDb->freeResult( $res );
// Print errors for missing blobs rows
foreach ( $xBlobIds as $blobId => $oldId ) {
$this->addError(
$this->addError( 'unfixable', "Error: unrecognised object class \"$className\"", $oldId );
}
}
- $dbr->freeResult( $res );
}
// Check local concat blob validity
unset( $concatBlobs[$row->old_id] );
}
- $dbr->freeResult( $res );
}
// Check targets of unresolved stubs
}
unset( $oldIds[$row->blob_id] );
}
- $extDb->freeResult( $res );
// Print errors for missing blobs rows
foreach ( $oldIds as $blobId => $oldIds2 ) {
public function provideGetWikiIdFromDomain() {
return [
- [ 'db-prefix', 'db-prefix' ],
+ [ 'db-prefix_', 'db-prefix_' ],
[ wfWikiID(), wfWikiID() ],
- [ new DatabaseDomain( 'db-dash', null, 'prefix' ), 'db-dash-prefix' ],
+ [ new DatabaseDomain( 'db-dash', null, 'prefix_' ), 'db-dash-prefix_' ],
[ wfWikiID(), wfWikiID() ],
- [ new DatabaseDomain( 'db-dash', null, 'prefix' ), 'db-dash-prefix' ],
- [ new DatabaseDomain( 'db', 'mediawiki', 'prefix' ), 'db-prefix' ], // schema ignored
- [ new DatabaseDomain( 'db', 'custom', 'prefix' ), 'db-custom-prefix' ],
+ [ new DatabaseDomain( 'db-dash', null, 'prefix_' ), 'db-dash-prefix_' ],
+ [ new DatabaseDomain( 'db', 'mediawiki', 'prefix_' ), 'db-prefix_' ], // schema ignored
+ [ new DatabaseDomain( 'db', 'custom', 'prefix_' ), 'db-custom-prefix_' ],
];
}
[ 'db', 'db', null, '' ],
[ 'db-schema-','db', 'schema', '' ],
[ 'db','db', 'mediawiki', '' ], // common b/c case
- [ 'db-prefix', 'db', null, 'prefix' ],
- [ 'db-schema-prefix', 'db', 'schema', 'prefix' ],
- [ 'db-prefix', 'db', 'mediawiki', 'prefix' ], // common b/c case
+ [ 'db-prefix_', 'db', null, 'prefix_' ],
+ [ 'db-schema-prefix_', 'db', 'schema', 'prefix_' ],
+ [ 'db-prefix_', 'db', 'mediawiki', 'prefix_' ], // common b/c case
// Bad hyphen cases (best effort support)
[ 'db-stuff', 'db-stuff', null, '' ],
- [ 'db-stuff-prefix', 'db-stuff', null, 'prefix' ],
+ [ 'db-stuff-prefix_', 'db-stuff', null, 'prefix_' ],
[ 'db-stuff-schema-', 'db-stuff', 'schema', '' ],
- [ 'db-stuff-schema-prefix', 'db-stuff', 'schema', 'prefix' ],
- [ 'db-stuff-prefix', 'db-stuff', 'mediawiki', 'prefix' ] // common b/c case
+ [ 'db-stuff-schema-prefix_', 'db-stuff', 'schema', 'prefix_' ],
+ [ 'db-stuff-prefix_', 'db-stuff', 'mediawiki', 'prefix_' ] // common b/c case
];
}
$db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
$this->assertEquals( 'foo', $db->tableName( 'foo' ) );
$this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
- $db->tablePrefix( 'foo' );
+ $db->tablePrefix( 'foo_' );
$this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
- $this->assertEquals( 'foobar', $db->tableName( 'bar' ) );
+ $this->assertEquals( 'foo_bar', $db->tableName( 'bar' ) );
}
/**
return Database::factory( 'SqliteMock', $p );
}
- function query( $sql, $fname = '', $tempIgnore = false ) {
+ function query( $sql, $fname = '', $flags = 0 ) {
return true;
}
return $s;
}
- public function query( $sql, $fname = '', $tempIgnore = false ) {
+ public function query( $sql, $fname = '', $flags = 0 ) {
$this->checkFunctionName( $fname );
- return parent::query( $sql, $fname, $tempIgnore );
+ return parent::query( $sql, $fname, $flags );
}
public function tableExists( $table, $fname = __METHOD__ ) {
] );
}
+ /**
+ * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::doSelectDomain
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::selectDB
+ * @covers \Wikimedia\Rdbms\DatabaseMssql::selectDB
+ * @covers DatabaseOracle::selectDB
+ */
public function testNiceDomains() {
global $wgDBname;
$factory->destroy();
}
+ /**
+ * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::doSelectDomain
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::selectDB
+ * @covers \Wikimedia\Rdbms\DatabaseMssql::selectDB
+ * @covers DatabaseOracle::selectDB
+ */
public function testTrickyDomain() {
global $wgDBname;
$factory = $this->newLBFactoryMulti(
[ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
[
- 'dbName' => 'do_not_select_me' // explodes if DB is selected
+ 'dbname' => 'do_not_select_me' // explodes if DB is selected
]
);
$lb = $factory->getMainLB();
$factory->destroy();
}
+ /**
+ * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::doSelectDomain
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::selectDB
+ * @covers \Wikimedia\Rdbms\DatabaseMssql::selectDB
+ * @covers DatabaseOracle::selectDB
+ */
public function testInvalidSelectDB() {
- // FIXME: fails under sqlite
- $this->markTestSkippedIfDbType( 'sqlite' );
+ if ( wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ $this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
+ }
+
$dbname = 'unittest-domain'; // explodes if DB is selected
$factory = $this->newLBFactoryMulti(
[ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
[
- 'dbName' => 'do_not_select_me' // explodes if DB is selected
+ 'dbname' => 'do_not_select_me' // explodes if DB is selected
]
);
$lb = $factory->getMainLB();
/** @var IDatabase $db */
$db = $lb->getConnection( DB_MASTER, [], '' );
- if ( $db->getType() === 'sqlite' ) {
+ \Wikimedia\suppressWarnings();
+ try {
$this->assertFalse( $db->selectDB( 'garbage-db' ) );
- } elseif ( $db->databasesAreIndependent() ) {
- try {
- $e = null;
- $db->selectDB( 'garbage-db' );
- } catch ( \Wikimedia\Rdbms\DBConnectionError $e ) {
- // expected
- }
- $this->assertInstanceOf( \Wikimedia\Rdbms\DBConnectionError::class, $e );
- $this->assertFalse( $db->isOpen() );
- } else {
- \Wikimedia\suppressWarnings();
- try {
- $this->assertFalse( $db->selectDB( 'garbage-db' ) );
- $this->fail( "No error thrown." );
- } catch ( \Wikimedia\Rdbms\DBExpectedError $e ) {
- $this->assertEquals(
- "Could not select database 'garbage-db'.",
- $e->getMessage()
- );
- }
- \Wikimedia\restoreWarnings();
+ $this->fail( "No error thrown." );
+ } catch ( \Wikimedia\Rdbms\DBQueryError $e ) {
+ $this->assertRegExp( '/[\'"]garbage-db[\'"]/', $e->getMessage() );
+ }
+ \Wikimedia\restoreWarnings();
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\DatabaseSqlite::selectDB
+ * @covers \Wikimedia\Rdbms\DatabasePostgres::selectDB
+ * @expectedException \Wikimedia\Rdbms\DBConnectionError
+ */
+ public function testInvalidSelectDBIndependant() {
+ $dbname = 'unittest-domain'; // explodes if DB is selected
+ $factory = $this->newLBFactoryMulti(
+ [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
+ [
+ 'dbname' => 'do_not_select_me' // explodes if DB is selected
+ ]
+ );
+ $lb = $factory->getMainLB();
+
+ if ( !wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ $this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
+ }
+
+ /** @var IDatabase $db */
+ $lb->getConnection( DB_MASTER, [], '' );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\DatabaseSqlite::selectDB
+ * @covers \Wikimedia\Rdbms\DatabasePostgres::selectDB
+ * @expectedException \Wikimedia\Rdbms\DBConnectionError
+ */
+ public function testInvalidSelectDBIndependant2() {
+ $dbname = 'unittest-domain'; // explodes if DB is selected
+ $factory = $this->newLBFactoryMulti(
+ [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
+ [
+ 'dbname' => 'do_not_select_me' // explodes if DB is selected
+ ]
+ );
+ $lb = $factory->getMainLB();
+
+ if ( !wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ $this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
}
+
+ $db = $lb->getConnection( DB_MASTER );
+ \Wikimedia\suppressWarnings();
+ $db->selectDB( 'garbage-db' );
+ \Wikimedia\restoreWarnings();
}
+ /**
+ * @covers \Wikimedia\Rdbms\LoadBalancer::getConnection
+ * @covers \Wikimedia\Rdbms\LoadBalancer::redefineLocalDomain
+ * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::selectDB
+ * @covers \Wikimedia\Rdbms\DatabaseMssql::selectDB
+ * @covers DatabaseOracle::selectDB
+ */
public function testRedefineLocalDomain() {
global $wgDBname;
);
unset( $conn1 );
- $factory->redefineLocalDomain( 'somedb-prefix' );
- $this->assertEquals( 'somedb-prefix', $factory->getLocalDomainID() );
+ $factory->redefineLocalDomain( 'somedb-prefix_' );
+ $this->assertEquals( 'somedb-prefix_', $factory->getLocalDomainID() );
- $domain = new DatabaseDomain( $wgDBname, null, 'pref' );
+ $domain = new DatabaseDomain( $wgDBname, null, 'pref_' );
$factory->redefineLocalDomain( $domain );
$n = 0;
public static function provideConstruct() {
return [
'All strings' =>
- [ 'foo', 'bar', 'baz', 'foo-bar-baz' ],
+ [ 'foo', 'bar', 'baz_', 'foo-bar-baz_' ],
'Nothing' =>
[ null, null, '', '' ],
'Invalid $database' =>
'Invalid $prefix' =>
[ 'foo', 'bar', 0, '', true ],
'Dash' =>
- [ 'foo-bar', 'baz', 'baa', 'foo?hbar-baz-baa' ],
+ [ 'foo-bar', 'baz', 'baa_', 'foo?hbar-baz-baa_' ],
'Question mark' =>
- [ 'foo?bar', 'baz', 'baa', 'foo??bar-baz-baa' ],
+ [ 'foo?bar', 'baz', 'baa_', 'foo??bar-baz-baa_' ],
];
}
'Basic' =>
[ 'foo', 'foo', null, '' ],
'db+prefix' =>
- [ 'foo-bar', 'foo', null, 'bar' ],
+ [ 'foo-bar_', 'foo', null, 'bar_' ],
'db+schema+prefix' =>
- [ 'foo-bar-baz', 'foo', 'bar', 'baz' ],
+ [ 'foo-bar-baz_', 'foo', 'bar', 'baz_' ],
'?h -> -' =>
- [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa' ],
+ [ 'foo?hbar-baz-baa_', 'foo-bar', 'baz', 'baa_' ],
'?? -> ?' =>
- [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+ [ 'foo??bar-baz-baa_', 'foo?bar', 'baz', 'baa_' ],
'? is left alone' =>
- [ 'foo?bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+ [ 'foo?bar-baz-baa_', 'foo?bar', 'baz', 'baa_' ],
'too many parts' =>
- [ 'foo-bar-baz-baa', '', '', '', true ],
+ [ 'foo-bar-baz-baa_', '', '', '', true ],
'from instance' =>
[ DatabaseDomain::newUnspecified(), null, null, '' ],
];
'Basic' =>
[ 'foo', 'foo', null, '' ],
'db+prefix' =>
- [ 'foo-bar', 'foo', null, 'bar' ],
+ [ 'foo-bar_', 'foo', null, 'bar_' ],
'db+schema+prefix' =>
- [ 'foo-bar-baz', 'foo', 'bar', 'baz' ],
+ [ 'foo-bar-baz_', 'foo', 'bar', 'baz_' ],
'?h -> -' =>
- [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa' ],
+ [ 'foo?hbar-baz-baa_', 'foo-bar', 'baz', 'baa_' ],
'?? -> ?' =>
- [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+ [ 'foo??bar-baz-baa_', 'foo?bar', 'baz', 'baa_' ],
'Nothing' =>
[ '', null, null, '' ],
];
'Basic' =>
[ 'foo', 'foo', null, '', true ],
'db+prefix' =>
- [ 'foo-bar', 'foo', null, 'bar', true ],
+ [ 'foo-bar_', 'foo', null, 'bar_', true ],
'db+schema+prefix' =>
- [ 'foo-bar-baz', 'foo', 'bar', 'baz', true ],
+ [ 'foo-bar-baz_', 'foo', 'bar', 'baz_', true ],
'db+dontcare_schema+prefix' =>
- [ 'foo-bar-baz', 'foo', null, 'baz', false ],
+ [ 'foo-bar-baz_', 'foo', null, 'baz_', false ],
'?h -> -' =>
- [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa', true ],
+ [ 'foo?hbar-baz-baa_', 'foo-bar', 'baz', 'baa_', true ],
'?? -> ?' =>
- [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa', true ],
+ [ 'foo??bar-baz-baa_', 'foo?bar', 'baz', 'baa_', true ],
'Nothing' =>
[ '', null, null, '', true ],
'dontcaredb+dontcaredbschema+prefix' =>
- [ 'mywiki-mediawiki-prefix', null, null, 'prefix', false ],
+ [ 'mywiki-mediawiki-prefix_', null, null, 'prefix_', false ],
'dontcaredb+schema+prefix' =>
- [ 'mywiki-schema-prefix', null, 'schema', 'prefix', false ],
+ [ 'mywiki-schema-prefix_', null, 'schema', 'prefix_', false ],
'db+dontcareschema+prefix' =>
- [ 'mywiki-schema-prefix', 'mywiki', null, 'prefix', false ],
+ [ 'mywiki-schema-prefix_', 'mywiki', null, 'prefix_', false ],
'postgres-db-jobqueue' =>
[ 'postgres-mediawiki-', 'postgres', null, '', false ]
];
public static function provideIsCompatible2() {
return [
'db+schema+prefix' =>
- [ 'mywiki-schema-prefix', 'thatwiki', 'schema', 'prefix' ],
+ [ 'mywiki-schema-prefix_', 'thatwiki', 'schema', 'prefix_' ],
'dontcaredb+dontcaredbschema+prefix' =>
- [ 'thatwiki-mediawiki-otherprefix', null, null, 'prefix' ],
+ [ 'thatwiki-mediawiki-otherprefix_', null, null, 'prefix_' ],
'dontcaredb+schema+prefix' =>
- [ 'mywiki-otherschema-prefix', null, 'schema', 'prefix' ],
+ [ 'mywiki-otherschema-prefix_', null, 'schema', 'prefix_' ],
'db+dontcareschema+prefix' =>
- [ 'notmywiki-schema-prefix', 'mywiki', null, 'prefix' ],
+ [ 'notmywiki-schema-prefix_', 'mywiki', null, 'prefix_' ],
];
}
}
/**
- * @covers Wikimedia\Rdbms\Database::registerTempTableOperation
+ * @covers Wikimedia\Rdbms\Database::registerTempTableWrite
*/
public function testSessionTempTables() {
$temp1 = $this->database->tableName( 'tmp_table_1' );
$oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Prefix is string' );
$this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
- $this->assertSame( $old, $this->db->tablePrefix( 'xxx' ) );
- $this->assertSame( 'xxx', $this->db->tablePrefix(), "Prefix set" );
+ $this->assertSame( $old, $this->db->tablePrefix( 'xxx_' ) );
+ $this->assertSame( 'xxx_', $this->db->tablePrefix(), "Prefix set" );
$this->db->tablePrefix( $old );
- $this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
+ $this->assertNotEquals( 'xxx_', $this->db->tablePrefix() );
$this->assertSame( $oldDomain, $this->db->getDomainId() );
$old = $this->db->dbSchema();
$oldSchema = $this->db->dbSchema();
$oldPrefix = $this->db->tablePrefix();
- $this->db->selectDomain( 'testselectdb-xxx' );
+ $this->db->selectDomain( 'testselectdb-xxx_' );
$this->assertSame( 'testselectdb', $this->db->getDBname() );
$this->assertSame( '', $this->db->dbSchema() );
- $this->assertSame( 'xxx', $this->db->tablePrefix() );
+ $this->assertSame( 'xxx_', $this->db->tablePrefix() );
$this->db->selectDomain( $oldDomain );
$this->assertSame( $oldDatabase, $this->db->getDBname() );
$this->assertSame( $oldPrefix, $this->db->tablePrefix() );
$this->assertSame( $oldDomain, $this->db->getDomainId() );
- $this->db->selectDomain( 'testselectdb-schema-xxx' );
+ $this->db->selectDomain( 'testselectdb-schema-xxx_' );
$this->assertSame( 'testselectdb', $this->db->getDBname() );
$this->assertSame( 'schema', $this->db->dbSchema() );
- $this->assertSame( 'xxx', $this->db->tablePrefix() );
+ $this->assertSame( 'xxx_', $this->db->tablePrefix() );
$this->db->selectDomain( $oldDomain );
$this->assertSame( $oldDatabase, $this->db->getDBname() );